﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using S7Cat.Generators.Data;
using System.Runtime.CompilerServices;
using System.Text;

namespace S7Cat.Generators
{
    [Generator]
    public class PlcStructGenerator : IIncrementalGenerator
    {
        public const string PlcStructAttributeFullName = "S7Cat.Attributes.PlcStructAttribute";

        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            var typeDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName(
                PlcStructAttributeFullName,
                predicate: static (node, token) =>
                {
                    return node is StructDeclarationSyntax;
                },
                transform: static (context, token) =>
                {
                    return (TypeDeclarationSyntax)context.TargetNode;
                }
            ).WithTrackingName("S7Cat.PlcStruct.1_ForAttributePlcStructAttribute");


            var source = typeDeclarations
                .Combine(context.CompilationProvider)
                .WithComparer(Comparer.Instance)
                .WithTrackingName("S7Cat.PlcStruct.2_S7CatUnionCombined");

            context.RegisterSourceOutput(source, static (context, source) =>
            {
                var (typeDeclaration, compilation) = source;
                Generate(typeDeclaration, compilation, context);
            });
        }

        public static void Generate(TypeDeclarationSyntax syntax, Compilation compilation, SourceProductionContext context)
        {
            var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree);
            var typeSymbol = semanticModel.GetDeclaredSymbol(syntax);

            if (typeSymbol == null) return;

            if (!IsPartial(syntax))
            {
                context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MustBePartial, syntax.Identifier.GetLocation(), typeSymbol.Name));
            }

            ushort byteCount = 0;
            string typeName = typeSymbol.Name;

            //var attribute = semanticModel.Compilation.GetTypeByMetadataName(PlcStructAttributeFullName);

            var properties = typeSymbol
                .GetMembers()
                .OfType<IPropertySymbol>()
                .Where(p => p.DeclaredAccessibility == Accessibility.Public && !p.IsIndexer)
                .Where(p =>
                {
                    if (p.GetMethod != null && p.SetMethod != null)
                    {
                        return p.Type.IsUnmanagedType;
                    }
                    else
                    {
                        return false;
                    }
                })
                .Select(p =>
                {
                    var typeName = p.Type.ToDisplayString();
                    var byteCount = ValueTypeNameToByteCount(typeName);

                    var expectName = $"S7Cat.Models.IPlcStruct<{p.Type.ToDisplayString()}>";
                    var isPlcStruct = p.Type.Interfaces.Any(i => i.ToDisplayString() == expectName);

                    return (p.Name, typeName, byteCount, isPlcStruct);
                });


            var serializeSb = new StringBuilder();
            var deserializeSb = new StringBuilder();
            var byteCountSb = new StringBuilder();

            byteCountSb.Append("public static ushort ByteCount => (ushort)(");
            deserializeSb.AppendLine($"""
                value = new {typeName}();
                int offset = 0;
                """);

            foreach (var (name, type, length, isPlcStruct) in properties)
            {
                if (isPlcStruct)
                {
                    serializeSb.AppendLine($"{type}.Serialize(value.{name}, writer);");
                    deserializeSb.AppendLine($"""
                        {type}.Deserialize(source.Slice(offset, (int){type}.ByteCount), out {type} __{name});
                        value.{name} = __{name};
                        offset += {type}.ByteCount;
                        """);
                    byteCountSb.Append($"{type}.ByteCount + ");
                }
                else if (length == 0) continue;
                else if (length == 1)
                {
                    serializeSb.AppendLine($"writer.Write(value.{name});");
                    deserializeSb.AppendLine($"""
                        value.{name} = source[{byteCount}];
                        offset++;
                        """);
                }
                else
                {
                    serializeSb.AppendLine($"BufferWriterExtensions.WriteBigEndian(writer, value.{name});");

                    deserializeSb.AppendLine($"""                    
                    source.Slice({byteCount}, {length}).Reverse();                    
                    value.{name} = Unsafe.ReadUnaligned<{type}>(ref source[{byteCount}]);
                    offset += {length};
                    """);
                }

                byteCount += length;
            }

            byteCountSb.Append($"{byteCount});");

            var code = $$"""
                using System;                
                using S7Cat.Tools;                
                using System.Buffers;
                using S7Cat.Models;
                using System.Runtime.CompilerServices;

                namespace {{typeSymbol.ContainingNamespace.ToDisplayString()}}
                {
                    public partial struct {{typeName}} : IPlcStruct<{{typeName}}>
                    {
                        {{byteCountSb}}

                        public static void Serialize(in {{typeName}} value, IBufferWriter<byte> writer)
                        {
                            {{serializeSb}}
                        }

                        public static void Deserialize(Span<byte> source, out {{typeName}} value)
                        {
                            {{deserializeSb}}
                        }
                    }
                }
                """;

            //Debugger.Launch();
            context.AddSource($"{typeName}.g.cs", SourceText.From(code, Encoding.UTF8));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsPartial(TypeDeclarationSyntax syntax)
        {
            return syntax.Modifiers.Any(SyntaxKind.PartialKeyword);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ushort ValueTypeNameToByteCount(string typeName)
        {
            return typeName switch
            {
                "sbyte" => sizeof(sbyte),
                "byte" => sizeof(byte),
                "short" => sizeof(short),
                "ushort" => sizeof(ushort),
                "int" => sizeof(int),
                "uint" => sizeof(uint),
                "long" => sizeof(long),
                "ulong" => sizeof(ulong),
                "float" => sizeof(float),
                "double" => sizeof(double),
                "decimal" => sizeof(decimal),
                "char" => sizeof(char),
                _ => 0
            };
        }
    }
}
